﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using MovieModel.Common;
using MovieModel.Model.Common;

namespace MovieModel.Model
{
    public abstract class Manager
    {
        public virtual IDictionary<int, Movie> Movies { get; protected set; }
        public virtual IDictionary<int, Person> People { get; protected set; }
        public virtual IDictionary<int, Company> Companies { get; protected set; }
        public virtual IDictionary<int, Tag> Tags { get; protected set; }

        public abstract void InitializeStorage();
        public abstract bool IsStorageReadyForInitialization();
        public abstract void Load(ProgressWatcher progress);
        public abstract void SaveMovies();
        public abstract void SaveTags();
        public abstract void SaveCompanies();
        public abstract void SavePeople();

        public abstract FileInfo ImportFile(Uri imageLocation, params ThumbnailRequest[] thumbnails);

        public class ThumbnailRequest
        {
            public int? Width { get; set; }
            public int? Height { get; set; }
            public FileInfo File { get; set; }

            public ThumbnailRequest(int? width, int? height)
            {
                if (width == null && height == null) throw new Exception("Width or Height must be non-null");
                Width = width;
                Height = height;
            }
        }
        
        public void AddMovie(Movie movie)
        {
            if (movie.Id != 0) throw new Exception("Cannot add a movie with a non-zero Id");
            movie.Id = NextId(Movies.Values);
            Movies[movie.Id] = movie;
            SaveMovies();
            movie.ItemUpdated += MovieUpdated;
            if (MovieAdded != null) MovieAdded(this, new ItemAddedEventArgs<Movie>(movie));
        }

        public void DeleteMovie(Movie movie)
        {
            if (movie.Id == 0) throw new Exception("Cannot delete a movie with a zero Id");
            Movies.Remove(movie.Id);
            SaveMovies();
            if (MovieRemoved != null) MovieRemoved(this, new ItemRemovedEventArgs<Movie>(movie)); 
        }

        protected void MovieUpdated(object sender, ItemUpdatedEventArgs<MovieItem<Movie>> e)
        {
            SaveMovies();
        }

        protected void TagUpdated(object sender, ItemUpdatedEventArgs<MovieItem<Tag>> e)
        {
            SaveTags();
        }

        protected void CompanyUpdated(object sender, ItemUpdatedEventArgs<MovieItem<Company>> e)
        {
            SaveCompanies();
        }

        protected void PersonUpdated(object sender, ItemUpdatedEventArgs<MovieItem<Person>> e)
        {
            SavePeople();
        }

        public IEnumerable<Tag> GetAndAddTags(IEnumerable<Tuple<int?, string>> tags)
        {
            bool anyAdded;
            var ret = GetAndAddItems(tags, Tags, TagAdded, out anyAdded);
            if (anyAdded) SaveTags();
            return ret;
        }
        public IEnumerable<Company> GetAndAddCompanies(IEnumerable<Tuple<int?, string>> companies)
        {
            bool anyAdded;
            var ret = GetAndAddItems(companies, Companies, CompanyAdded, out anyAdded);
            if (anyAdded) SaveCompanies();
            return ret;
        }
        public IEnumerable<Person> GetAndAddPeople(IEnumerable<Tuple<int?, string>> people)
        {
            bool anyAdded;
            var ret = GetAndAddItems(people, People, PersonAdded, out anyAdded);
            if (anyAdded) SavePeople();
            return ret;
        }

        /// <summary>
        /// Adds all the items into the manager.  If the item already has a non-zero ID then it is not added
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="tuples"></param>
        /// <param name="dict"></param>
        /// <param name="addEvent"></param>
        /// <param name="anyAdded">True if any items were added, false otherwise</param>
        /// <returns></returns>
        /// 
        private static IEnumerable<T> GetAndAddItems<T>(
            IEnumerable<Tuple<int?, string>> tuples, 
            IDictionary<int, T> dict, 
            EventHandler<ItemAddedEventArgs<T>> addEvent, 
            out bool anyAdded) where T : MovieItem<T>, new()
        {
            anyAdded = false;
            int nextId = NextId(dict.Values);
            var ret = new List<T>();
            foreach (var tuple in tuples)
            {
                T item;
                if (tuple.Item1 == null)
                {
                    item = new T {Id = nextId++, Name = tuple.Item2};
                    anyAdded = true;
                    dict[item.Id] = item;
                    if (addEvent != null) addEvent(null, new ItemAddedEventArgs<T>(item));
                }
                else
                {
                    item = dict[tuple.Item1.Value];
                }
                ret.Add(item);
            }
            return ret;
        }

        private static int NextId<T>(IEnumerable<T> items) where T : MovieItem<T>, new()
        {
            Contract.Requires(items != null);
            if (!items.Any()) return 1;
            return items.Select(i => i.Id).Max() + 1;
        }

        public event EventHandler<ItemAddedEventArgs<Movie>> MovieAdded;
        public event EventHandler<ItemRemovedEventArgs<Movie>> MovieRemoved; 
        public event EventHandler<ItemAddedEventArgs<Tag>> TagAdded;
        public event EventHandler<ItemAddedEventArgs<Person>> PersonAdded;
        public event EventHandler<ItemAddedEventArgs<Company>> CompanyAdded;
    }
}
